/*-----------------------------------------------------------------------------
	The contents of this file are subject to the Mozilla Public License
	Version 1.1 (the "License"); you may not use this file except in
	compliance with the License.You may obtain a copy of the License at
	http://www.mozilla.org/MPL/MPL-1.1.html
	
	Software distributed under the License is distributed on an "AS IS"
	basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
	License for the specific language governing rights and limitations
	under the License.
	
	The Original Code is Fever Framework code.
	
	The Initial Developer of the Original Code is Romain Ecarnot.
	Portions created by Initial Developer are Copyright (C) 2006
	the Initial Developer. All Rights Reserved.
	
	Contributor(s):
	
	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	See the License for the specific language governing permissions and
	limitations under the License.
-----------------------------------------------------------------------------*/

import com.bourre.commands.Delegate;
import com.bourre.core.AccessorFactory;
import com.bourre.core.HashCodeFactory;
import com.bourre.core.IAccessor;
import com.bourre.data.collections.Map;
import com.bourre.data.iterator.Iterator;
import com.bourre.data.libs.ConfigLoader;
import com.bourre.data.libs.LibStack;
import com.bourre.data.libs.XMLToObject;
import com.bourre.data.libs.XMLToObjectDeserializer;
import com.bourre.data.libs.XMLToObjectEvent;
import com.bourre.events.EventType;
import com.bourre.events.IEvent;

import fever.app.local.Culture;
import fever.app.local.Local;
import fever.app.local.LocalisationEvent;
import fever.app.local.LocalisationListener;
import fever.core.Resources;
import fever.events.PriorityEventBroadcaster;
import fever.Fever;
import fever.log.FeverDebug;
import fever.utils.StringUtil;

/**
 * Localisation system manager.
 * 
 * <p>Defines default translations for specific control using 
 * {@link fever.core.Resources} implementation.
 *  
 * @author Romain Ecarnot
 */
class fever.app.local.Localisation extends PriorityEventBroadcaster
{
	//-------------------------------------------------------------------------
	// Events definition
	//-------------------------------------------------------------------------
	
	/** Broadcasted when lang change. */
	public static var onUpdateEVENT : EventType = LocalisationEvent.onUpdateEVENT;
	
		
	//-------------------------------------------------------------------------
	// Private properties
	//-------------------------------------------------------------------------
	
	private static var _instance : Localisation;
	private static var _initialized : Boolean = false;
	
	private var _handler : Delegate;
	
	private var _clientLang : String;
	private var _connected : Boolean;
	private var _current : String;	private var _currentCulture : Culture;
	private var _loaded : Boolean;
	private var _loaderStack : LibStack;
	private var _localMap : Map;	private var _cultureMap : Map;
	private var _repository : Object;
	
	private var _accessorMap : Map;
	private var _accessor : IAccessor;
	
	
	//-------------------------------------------------------------------------
	// Public Properties
	//-------------------------------------------------------------------------
	
	/** Current used localisation. */
	public static function get lang() : String { return getInstance()._current; }	public static function set lang( id : String ) : Void { getInstance()._selectLanguage( id ); }
	
	/** Current used Culture */
	public static function get culture() : Culture { return getInstance()._currentCulture; }
	
	/** Property name identifier for sharedObject saving. */
	public static function get CONFIG_ID() : String { return 'localisation'; }
	
	
	//-------------------------------------------------------------------------
	// Public API
	//-------------------------------------------------------------------------
	
	/**
	 * Inits localisation system.
	 * 
	 * <p>Automatically call by configuration API.
	 */
	public static function init( langList : Object, handler : Delegate ) : Void
	{
		if( _initialized ) return;
		
		_initialized = true;
		var ins : Localisation = getInstance();
		ins._handler = handler;
		
		for( var s : String in langList )
		{
			var lang : Object = langList[ s ];
			ins._push( new Culture( lang.id, lang.label ) );
		}
		
		ins._load();
	}
	
	/**
	 * Retreives passed-in {@code id} local resource in current 
	 * language map.
	 * 
	 * @param id Translation node id to retreive
	 * @param resource ( optional ) custom resource where to search the {@code id}
	 * 
	 * @return resource string or {@code null} if {@code id} is not defined 
	 * in map.
	 */
	public static function getResource( id : String, resource : Resources )
	{
		return getInstance()._getResource( id, resource );
	}
	
	/**
	 * Returns {@code true} if passed-in {@code id} exists in Localisation data.
	 * 
	 * <p>Pass full node namespace to search in global Localisation object.<br />
	 * Either specify {@code resource} parameter to search in dedicated resource.
	 * ( use local node name )
	 * 
	 * <p>Example
	 * 	var b : Boolean = hasResource( 'fvaswing.alert.okLabel ');
	 * 	var b : Boolean = hasResource( 'okLabel', FvAlertResources.getInstance() );
	 * 	
	 * 	@param id Translation node id
	 * 	@param resource ( optional ) Custom resource where to search for
	 */
	public static function hasResource( id : String, resource : Resources ) : Boolean
	{
		return ( getInstance()._getResource( id, resource ) != undefined );
	}
	
	/**
	 * Registers an {@code instance} property by his passed-in 
	 * {@code setter} method.
	 * 
	 * @param instance * Target instance
	 * @param setter {@code String} or {code Function} property access
	 * @param id Language key to watch
	 * @param resource Custom resource where to find {@code id} node
	 * @param substitution ( optional ) if apply, use a {@code StringUtil.substitute()} 
	 * to return translation.
	 */
	public static function connectInstance( instance : Object, setter, id : String, resource : Resources, substitution : Object ) : Void
	{
		var accessor : IAccessor = AccessorFactory.getInstance( instance, setter, null );
		getInstance()._connect( accessor, id, resource, substitution );
	}
	
	/**
	 * Registers an {@code IAccessor} instance
	 * 
	 * @param accessor IAccessor instance
	 * @param id Language key to watch
	 * @param resource Custom resource where to find {@code id} node
	 * @param substitution ( optional ) if apply, use a {@code StringUtil.substitute()} 
	 * to return translation.
	 */
	public static function connectAccessor( accessor : IAccessor, id : String, resource : Resources, substitution : Object ) : Void
	{
		getInstance()._connect( accessor, id, resource, substitution );	
	}
	
	/**
	 * Unregisters instance property.
	 * 
	 * @param instance * Target instance
	 */
	public static function disconnectInstance( instance : Object ) : Void
	{
		getInstance()._disconnect( instance );
	}
	
	/**
	 * Unregisters accessor.
	 * 
	 * @param instance IAccessor instance
	 */
	public static function disconnectAccessor( accessor : IAccessor ) : Void
	{
		getInstance()._disconnect( accessor.getTarget() );
	}
	
	/** 
	 * Returns {@code true} if resources files are loaded.
	 */
	public static function isLoaded() : Boolean
	{ 
		return getInstance()._loaded;
	}
	
	/**
	 * Adds passed-in {@code listener} for receiving localisation events.
	 * 
	 * @param listener {@link fever.app.local.LocalisationListener} instance
	 */
	public static function addLocalisationListener( listener : LocalisationListener, priority : Number ) : Void
	{
		getInstance().addListener( listener, null, priority );
		listener.onLocalisationUpdate();
	}
	
	/**
	 * Removes passed-in {@code listener} for receiving localisation events.
	 * 
	 * @param listener {@link fever.app.local.LocalisationListener} instance
	 */
	public static function removeLocalisationListener( listener : LocalisationListener ) : Void
	{
		getInstance().removeListener( listener );
	}
	
	/**
	 * Broadcasts a {@code LocalisationEvent.UPDATE} event.
	 * 
	 * <p>All connected objects and listeners are update.
	 */
	public static function update() : Void
	{
		getInstance()._update();
	}
	
	/**
	 * Returns all available localisation.
	 */
	public static function getLocalisationList() : Array
	{
		var it : Iterator = getInstance()._cultureMap.getValuesIterator();
		var a : Array = new Array();
		
		while( it.hasNext() ) a.push(  it.next() );
		return a;	
	}
	
	/**
	 * Returns if localisation is supported in application.
	 * 
	 * <p>Returns {@code false} if no translation file is found.
	 */
	public static function isSupported() : Boolean
	{
		return !getInstance()._cultureMap.isEmpty();	
	}
	
	
	//-------------------------------------------------------------------------
	// Private implementation
	//-------------------------------------------------------------------------
	
	/**
	 * Returns {@code Localisation} instance.
	 * 
	 * <p>Always return the same instance.
	 * 
	 * @return {@code Localisation} instance
	 */
	private static function getInstance() : Localisation
	{
		if( !_instance ) _instance = new Localisation();
		return _instance;
	}
	
	/**
	 * Constructor.
	 */
	private function Localisation()
	{
		_clientLang = Fever.client.language;
		
		_localMap = new Map();		_cultureMap = new Map();
		_repository = new Object();
		_accessorMap = new Map();
		
		_connected = false;
		_loaded = false;
	}
	
	private function _push( culture : Culture ) : Void
	{
		var id : String = culture.id;
		var link : String = Fever.application.config.getLocalePath() + id + '.locale';
		
		if( _localMap.containsKey( id ) )
			FeverDebug.WARN( 'Language id : " + id + " is already registred' );
		else 
		{
			_cultureMap.put( id, culture );
			_localMap.put( id, link );
		}
	}
	
	private function _load( id : String ) : Void
	{
		if( _localMap.isEmpty() )
		{
			FeverDebug.WARN( 'Localisation map is empty' );
			
			 _onLoadComplete( _clientLang );
			 return;
		}
		
		_loaderStack = new LibStack();
		
		var iterator : Iterator = _localMap.getKeysIterator();
		while( iterator.hasNext() )
		{
			var key : String = iterator.next();
			var link : String = _localMap.get( key );
			var local : Local = new Local( key );
			var loader : ConfigLoader = new ConfigLoader( local );
			loader.getContent().addEventListener( XMLToObject.onErrorEVENT, this, _onError, key );
			
			_loaderStack.enqueue( loader, key, link );
							
			_repository[key] = local;
		}	
		
		_loaderStack.addEventListener( 
			LibStack.onLoadCompleteEVENT, this, _onLoadConfig, id );
		
		if( !_loaderStack.isEmpty() ) _loaderStack.load();
		else _onLoadComplete( null );
	}
	
	private function _connect( accessor : IAccessor, id : String, resource : Resources, substitution : Object ) : Void
	{
		if( _accessorMap.isEmpty() ) _connected = true;
		
		var key : Number = HashCodeFactory.getKey( accessor.getTarget() );
		if( !_accessorMap.containsKey( key ) )
		{
			_accessorMap.put( key, { access : accessor, id : id, resource : resource, substitution : substitution } );
			
			if( _loaded || resource )
			{
				if( substitution )
				{
					if( substitution instanceof Array )
					{
						accessor.setValue( StringUtil.substituteByArray( _getResource( id, resource ), Array( substitution ) ) );
					}
					else
					{
						accessor.setValue( StringUtil.substitute( _getResource( id, resource ), substitution ) );
					}
				}
				else accessor.setValue( _getResource( id, resource ) );
			}
		}
		else FeverDebug.ERROR( accessor.getTarget() + ' is already connect to localisation' );
	}
	
	private function _disconnect ( instance : Object ) : Void
	{
		if( !instance ) return;
		
		_accessorMap.remove( HashCodeFactory.getKey(instance) );
		
		if( _accessorMap.isEmpty()) _connected = false;
	}
	
	private function _update() : Void
	{
		broadcastEvent( new LocalisationEvent( Localisation.onUpdateEVENT, this) );
		
		if(_connected)
		{
			var iterator : Iterator = _accessorMap.getValuesIterator();
			while( iterator.hasNext() )
			{
				var o : Object = iterator.next();
				var f : IAccessor = o.access;
				var id : String = o.id;
				var res : Resources = o.resource;
				var sub : Object = o.substitution;
				
				if( sub )
				{
					if( sub instanceof Array )
					{
						f.setValue( StringUtil.substituteByArray( _getResource( id, res ), Array( sub ) ) );
					}
					else
					{
						f.setValue( StringUtil.substitute( _getResource( id, res ), sub ) );
					}
				}
				else f.setValue( _getResource( id, res ) );
			}
		}
	}
	
	private function _getConfigDeserializer( o : ConfigLoader ) : XMLToObjectDeserializer
	{
		return XMLToObjectDeserializer( XMLToObject( o.getContent() ).getDeserializer() );
	}
	
	private function _onLoadConfig(event : IEvent, id : String) : Void
	{
		_onLoadComplete( id );
	}
	
	private function _onLoadComplete( id : String ) : Void
	{
		_loaded = true;
		_loadDefaultLanguage( id );
		
		_callHander();
	}
	
	private function _onError( event : XMLToObjectEvent, id  : String ) : Void
	{
		FeverDebug.ERROR( 'Localisation error ' + event.getLib().getURL() ) ;
		
		_loaded = false;
		XMLToObject( event.getTarget() ).release();
		XMLToObject( event.getTarget() ).removeEventListener( XMLToObject.onErrorEVENT, this );
		_loaderStack.release();
		_loaderStack.removeEventListener( LibStack.onLoadCompleteEVENT, this  );
		
		_cultureMap.remove( id );
		_callHander();
	}
	
	private function _loadDefaultLanguage( id : String ) : Void
	{
		var saved : String = Fever.application.config.load( CONFIG_ID );
		
		if( _repository[ saved ] != undefined )
		{
			_applyNewLang( saved );
		}
		else _selectLanguage( _clientLang );
	}
	
	private function _selectLanguage( id : String ) : Void
	{
		if( _repository[id] == undefined )
		{
			if( _repository[ _clientLang ] == undefined ) id = _current;	
			else id = _clientLang;
		}
		
		_applyNewLang( id );
	}
	
	private function _applyNewLang( id : String ) : Void
	{
		if( id != _current )
		{
			_current = id;
			_currentCulture = _cultureMap.get( _current );
			update();
		}
	}
	
	private function _getResource( id : String, resource : Resources )
	{
		if( resource )
		{
			return resource.getResource( id );	
		}
		else
		{
			var oList : Array = id.split( '.' );
			var l : Number = oList.length;
			var o = _repository[ _current ];
			
			for( var i : Number = 0; i < l; i++ ) o = o[ oList[i] ];
			if( !o ) FeverDebug.ERROR( id + ' is undefined in ' + _current + ' culture' );
			return o;
		}
		return null;
	}
	
	private function _callHander( ) : Void
	{
		_handler.setArguments( [_loaded] );
		_handler.execute();	
	}
}